/*
 * Copyright (c) 2009. Silenus Consultoria, S.L.
 */
package es.silenus.detecta.service.impl;

import es.silenus.detecta.dao.OrganizationDAO;
import es.silenus.detecta.dao.Page;
import es.silenus.detecta.dao.UserDAO;

import es.silenus.detecta.model.Organization;
import es.silenus.detecta.model.User;

import es.silenus.detecta.service.AlfrescoService;
import es.silenus.detecta.service.OrganizationService;
import es.silenus.detecta.service.UserService;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.InitializingBean;

import org.springframework.dao.DataAccessException;

import org.springframework.ldap.NamingException;

import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.simple.AbstractParameterizedContextMapper;
import org.springframework.ldap.core.simple.SimpleLdapTemplate;

import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;

import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;

import org.springframework.security.context.SecurityContextHolder;

import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.ldap.LdapUsernameToDnMapper;

import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.encoding.PasswordEncoder;

import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;

import org.springframework.security.util.AuthorityUtils;

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;


/**
 * LDAP user service.
 *
 * @author <a href="mailto:malonso@silenus-consultoria.es">Mariano Alonso</a>
 *
 * @since 25-jun-2009 13:19:46
 */
public class LDAPUserService implements UserService, OrganizationService, InitializingBean {
	/**
	 * Logger for this class.
	 */
	private static final Log LOG = LogFactory.getLog(LDAPUserService.class);

	/**
	 * NO AUTHORITIES.
	 */
	private static final GrantedAuthority[] NO_AUTHORITIES = new GrantedAuthority[0];

	/**
	 * The object class.
	 */
	private static final String OBJECT_CLASS = "objectClass";

	/**
	 * The password encoder.
	 */
	private PasswordEncoder passwordEncoder;

	/**
	 * The user DAO.
	 */
	private UserDAO userDAO;

	/**
	 * The organization DAO.
	 */
	private OrganizationDAO organizationDAO;

	/**
	 * The alfresco service.
	 */
	private AlfrescoService alfrescoService;

	/**
	 * Template.
	 */
	private SimpleLdapTemplate template;

	/**
	 * The user search base.
	 */
	private String userBase;

	/**
	 * The user name attribute (cn or uid)
	 */
	private String userNameAttribute;

	/**
	 * The user password attribute (userPassword).
	 */
	private String userPasswordAttribute;

	/**
	 * The user name mapper.
	 */
	private LdapUsernameToDnMapper userNameMapper;

	/**
	 * The user details mapper.
	 */
	private LDAPUserMapper userDetailsMapper;

	/**
	 * The group search base.
	 */
	private String groupBase;

	/**
	 * The group search filter.
	 */
	private String groupFilter;

	/**
	 * The group type.
	 */
	private String groupType;

	/**
	 * The group name attribute (cn).
	 */
	private String groupNameAttribute;

	/**
	 * The group member attribute (member or uniqueMember).
	 */
	private String groupMemberAttribute;

	/**
	 * The group name mapper.
	 */
	private LdapUsernameToDnMapper groupNameMapper;

	/**
	 * The group mapper.
	 */
	private LDAPOrganizationMapper organizationMapper;

	/**
	 * Flag to manage users and groups with alfresco web service.
	 */
	private boolean manageUsers;

/**
   * Constructor.
   */
	public LDAPUserService() {
		// User default values
		userNameAttribute = "uid";
		userPasswordAttribute = "userPassword";

		// Group default values
		groupType = "posixGroup";
		groupFilter = new EqualsFilter(OBJECT_CLASS, groupType).encode();
		groupNameAttribute = "cn";
		groupMemberAttribute = "memberUid";
		manageUsers = true;
	}

	/**
	 * Sets the context source.
	 *
	 * @param contextSource the context source.
	 */
	public void setContextSource(final ContextSource contextSource) {
		this.template = new SimpleLdapTemplate(contextSource);
	}

	/**
	 * Sets the password encoder.
	 *
	 * @param passwordEncoder the password encoder.
	 */
	public void setPasswordEncoder(final PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

	/**
	 * Sets the user DAO.
	 *
	 * @param userDAO the user DAO.
	 */
	public void setUserDAO(final UserDAO userDAO) {
		this.userDAO = userDAO;
	}

	/**
	 * Sets the organization DAO.
	 *
	 * @param organizationDAO the organization DAO.
	 */
	public void setOrganizationDAO(final OrganizationDAO organizationDAO) {
		this.organizationDAO = organizationDAO;
	}

	/**
	 * Sets the alfresco service.
	 *
	 * @param alfrescoService the alfresco service.
	 */
	public void setAlfrescoService(final AlfrescoService alfrescoService) {
		this.alfrescoService = alfrescoService;
	}

	/**
	 * Sets the user search base.
	 *
	 * @param userBase the user search base.
	 */
	public void setUserBase(final String userBase) {
		this.userBase = userBase;
	}

	/**
	 * Sets the user name attribute (cn or uid).
	 *
	 * @param userNameAttribute the user name attribute.
	 */
	public void setUserNameAttribute(final String userNameAttribute) {
		this.userNameAttribute = userNameAttribute;
	}

	/**
	 * Sets the user password attribute (userPassword).
	 *
	 * @param userPasswordAttribute the user password attribute.
	 */
	public void setUserPasswordAttribute(final String userPasswordAttribute) {
		this.userPasswordAttribute = userPasswordAttribute;
	}

	/**
	 * Sets the user details mapper.
	 *
	 * @param userDetailsMapper the user details mapper.
	 */
	public void setUserDetailsMapper(final LDAPUserMapper userDetailsMapper) {
		this.userDetailsMapper = userDetailsMapper;
	}

	/**
	 * Sets the group base search DN.
	 *
	 * @param groupBase the group base search DN.
	 */
	public void setGroupBase(final String groupBase) {
		this.groupBase = groupBase;
	}

	/**
	 * Sets the group type  (groupOfNames or groupOfUniqueNames).
	 *
	 * @param groupType the group type.
	 */
	public void setGroupType(final String groupType) {
		this.groupType = groupType;
		this.groupFilter = new EqualsFilter(OBJECT_CLASS, groupType).encode();
	}

	/**
	 * Sets the group name attribute (cn).
	 *
	 * @param groupNameAttribute the group name attribute.
	 */
	public void setGroupNameAttribute(final String groupNameAttribute) {
		this.groupNameAttribute = groupNameAttribute;
	}

	/**
	 * Sets the group member attribute (member or uniqueMember).
	 *
	 * @param groupMemberAttribute the group member attribute.
	 */
	public void setGroupMemberAttribute(final String groupMemberAttribute) {
		this.groupMemberAttribute = groupMemberAttribute;
	}

	/**
	 * Sets the group mapper.
	 *
	 * @param organizationMapper the group mapper.
	 */
	public void setGroupMapper(final LDAPOrganizationMapper organizationMapper) {
		this.organizationMapper = organizationMapper;
	}

	/**
	 * Sets the manage users flag.
	 *
	 * @param manageUsers the manage users flag.
	 */
	public void setManageUsers(boolean manageUsers) {
		this.manageUsers = manageUsers;
	}

	/**
	 * Invoked by a BeanFactory after it has set all bean properties supplied (and satisfied BeanFactoryAware and
	 * ApplicationContextAware).<p>This method allows the bean instance to perform initialization only possible
	 * when all bean properties have been set and to throw an exception in the event of misconfiguration.</p>
	 *
	 * @throws Exception in the event of misconfiguration (such as failure to set an essential property) or if
	 * 				 initialization fails.
	 */
	public void afterPropertiesSet() throws Exception {
		this.userNameMapper = new DefaultLdapUsernameToDnMapper(userBase, userNameAttribute);
		this.groupNameMapper = new DefaultLdapUsernameToDnMapper(groupBase, groupNameAttribute);
	}

	/**
	 * Locates a user by its internal identifier.
	 *
	 * @param id the internal identifier.
	 *
	 * @return the user.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public User findUserById(Integer id) {
		return userDAO.findById(id);
	}

	/**
	 * Modify the current user's password. This should change the user's password in the persistent user repository
	 * (datbase, LDAP etc) and should also modify the current security context to contain the new password.
	 *
	 * @param userName the user name.
	 * @param oldPassword current password (for re-authentication if required)
	 * @param newPassword the password to change to
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void changePassword(String userName, String oldPassword, String newPassword) {
		final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

		if(LOG.isDebugEnabled()) {
			LOG.debug("Changing password of user \"" + userName + "\"");
		}

		// Salt password from userName.getBytes() ?
		String newPasswordEncoded = passwordEncoder.encodePassword(newPassword, null);

		try {
			Authentication tempAuthentication = new UsernamePasswordAuthenticationToken(
					userName, oldPassword, NO_AUTHORITIES);

			SecurityContextHolder.getContext().setAuthentication(tempAuthentication);

			this.changePassword(oldPassword, newPasswordEncoded);
		} finally {
			SecurityContextHolder.getContext().setAuthentication(authentication);
		}

		User theUser = userDAO.findByLogin(userName);

		theUser.setPassword(newPasswordEncoded);
		userDAO.update(theUser);
	}

	/**
	 * Finds the objects that satisfy a search criteria expressed as an example object bean and a pagination object
	 * to limit and sort.
	 *
	 * @param sample the sample criteria.
	 * @param start the start index.
	 * @param limit the limit index.
	 * @param sortFields the sort fields.
	 * @param sortDirections the sort directions.
	 *
	 * @return the page.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public Page<User> findByExample(
		Map<String, Object> sample, int start, int limit, String[] sortFields, String[] sortDirections) {
		return userDAO.findByExample(sample, start, limit, sortFields, sortDirections);
	}

	/**
	 * Locates the user based on the username. In the actual implementation, the search may possibly be case
	 * insensitive, or case insensitive depending on how the implementaion instance is configured. In this case, the
	 * <code>UserDetails</code> object that comes back may have a username that is of a different case than what was
	 * actually requested..
	 *
	 * @param username the username presented to the {@link
	 * 				org.springframework.security.providers.dao.DaoAuthenticationProvider}
	 *
	 * @return a fully populated user record (never <code>null</code>)
	 *
	 * @throws UsernameNotFoundException if the user could not be found or the user has no GrantedAuthority
	 * @throws DataAccessException if user could not be found for a repository-specific reason
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public User loadUserByUsername(final String username) throws UsernameNotFoundException, DataAccessException {
		final DistinguishedName dn = userNameMapper.buildDn(username);

		if(LOG.isDebugEnabled()) {
			LOG.debug("Loading user \"" + username + "\"");
		}

		final GrantedAuthority[] authorities = getUserAuthorities(dn, username);

		User result;

		try {
			result = template.lookup(
					dn,
					new AbstractParameterizedContextMapper<User>() {
						/**
						 * Map a single <code>DirContextOperation</code> to an object. The supplied instance is the
						 * object supplied to {@link #mapFromContext(Object)} cast to a <code>DirContextOperations</code>.
						 *
						 * @param ctx the context to map to an object.
						 *
						 * @return an object built from the data in the context.
						 */
						protected User doMapFromContext(DirContextOperations ctx) {
							return userDetailsMapper.mapUserFromContext(ctx, username, authorities);
						}
					});
		} catch(NamingException e) {
			throw new UsernameNotFoundException("User \"" + username + "\" not found", e);
		}

		return result;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param dn the distinguished name of the entry - may be either relative to the base context or a complete DN
	 * 				including the name of the context (either is supported).
	 * @param username the user whose roles are required.
	 *
	 * @return the granted authorities returned by the group search
	 */
	private GrantedAuthority[] getUserAuthorities(final DistinguishedName dn, final String username) {
		if(LOG.isDebugEnabled()) {
			LOG.debug("Retrieving user groups in LDAP for user \"" + username + "\"");
		}

		AndFilter filter = new AndFilter();

		filter.and(new EqualsFilter(groupMemberAttribute, dn.toCompactString()));
		filter.and(new EqualsFilter(OBJECT_CLASS, groupType));

		List<GrantedAuthority> authorities = template.search(
				groupBase, filter.encode(),
				new AbstractParameterizedContextMapper<GrantedAuthority>() {
					/**
					 * Map a single <code>DirContextOperation</code> to an object. The supplied instance is the
					 * object supplied to {@link #mapFromContext(Object)} cast to a <code>DirContextOperations</code>.
					 *
					 * @param ctx the context to map to an object.
					 *
					 * @return an object built from the data in the context.
					 */
					protected GrantedAuthority doMapFromContext(DirContextOperations ctx) {
						return new GrantedAuthorityImpl(ctx.getStringAttribute(groupNameAttribute));
					}
				});

		return authorities.toArray(new GrantedAuthority[authorities.size()]);
	}

	/**
	 * Create a new user with the supplied details.
	 *
	 * @param user the user.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void createUser(UserDetails user) {
		User theUser = (User)user;

		if(LOG.isDebugEnabled()) {
			LOG.debug("Creating user: \"" + theUser.getLogin() + "\"");
		}

		// Persist
		userDAO.save(theUser);

		final String userName = user.getUsername();
		final String groupName = theUser.getOrganization().getName();

		final DistinguishedName userDN = userNameMapper.buildDn(userName);

		// Creates a user
		final DirContextAdapter ctx = new DirContextAdapter(userDN);

		userDetailsMapper.mapUserToContext(theUser, ctx);
		template.bind(ctx);

		if(manageUsers) {
			// Create alfresco user
			alfrescoService.createUser(user);
		}
		// Add user to group
		addUserToGroup(userName, groupName);
	}

	private String[] getUserGroupNames(final DistinguishedName userDN) {
		AndFilter filter = new AndFilter();

		filter.and(new EqualsFilter(OBJECT_CLASS, groupType));
		filter.and(new EqualsFilter(groupMemberAttribute, userDN.toCompactString()));

		List<String> groupNames = template.search(
				groupBase, filter.encode(),
				new AbstractParameterizedContextMapper<String>() {
					/**
					 * Map a single <code>DirContextOperation</code> to an object. The supplied instance is the
					 * object supplied to {@link #mapFromContext(Object)} cast to a <code>DirContextOperations</code>.
					 *
					 * @param ctx the context to map to an object.
					 *
					 * @return an object built from the data in the context.
					 */
					protected String doMapFromContext(DirContextOperations ctx) {
						return ctx.getStringAttribute(groupNameAttribute);
					}
				});

		return groupNames.toArray(new String[groupNames.size()]);
	}

	private String getUserGroup(final DistinguishedName userDN) {
		AndFilter filter = new AndFilter();

		filter.and(new EqualsFilter(OBJECT_CLASS, groupType));
		filter.and(new EqualsFilter(groupMemberAttribute, userDN.toCompactString()));

		return template.searchForObject(
			groupBase, filter.encode(),
			new AbstractParameterizedContextMapper<String>() {
				/**
				 * Map a single <code>DirContextOperation</code> to an object. The supplied instance is the object
				 * supplied to {@link #mapFromContext(Object)} cast to a <code>DirContextOperations</code>.
				 *
				 * @param ctx the context to map to an object.
				 *
				 * @return an object built from the data in the context.
				 */
				protected String doMapFromContext(DirContextOperations ctx) {
					return ctx.getStringAttribute(groupNameAttribute);
				}
			});
	}

	/**
	 * Update the specified user.
	 *
	 * @param user the user.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void updateUser(UserDetails user) {
		User theUser = (User)user;

		final String userName = user.getUsername();
		final String groupName = theUser.getOrganization().getName();

		if(LOG.isDebugEnabled()) {
			LOG.debug("Updating user: \"" + userName + "\"");
		}

		final DistinguishedName userDN = userNameMapper.buildDn(userName);

		// Remove user from previous group if required
		String[] groups = getUserGroupNames(userDN);

		for(String group : groups) {
			removeUserFromGroup(userName, group);
		}

		// Persist
		userDAO.update(theUser);

		// Updates a user
		final DirContextAdapter ctx = (DirContextAdapter)template.lookupContext(userDN);

		ctx.setUpdateMode(true);
		userDetailsMapper.mapUserToContext(theUser, ctx);
		template.modifyAttributes(ctx);

		if(manageUsers) {
			// Update alfresco user
			alfrescoService.updateUser(theUser);
		}

		// Add user to group
		addUserToGroup(userName, groupName);
	}

	/**
	 * Remove the user with the given login name from the system.
	 *
	 * @param username the user name.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void deleteUser(final String username) {
		if(LOG.isDebugEnabled()) {
			LOG.debug("Removing user: \"" + username + "\"");
		}

		final DistinguishedName userDN = userNameMapper.buildDn(username);

		String[] groups = getUserGroupNames(userDN);

		for(String group : groups) {
			removeUserFromGroup(username, group);
		}

		template.unbind(userDN.toCompactString());

		if(manageUsers) {
			// Remove alfresco user
			alfrescoService.removeUser(username);
		}

		// Remove local user
		User user = userDAO.findByLogin(username);

		if(user != null) {
			userDAO.remove(user);
		}
	}

	/**
	 * Modify the current user's password. This should change the user's password in the persistent user repository
	 * (datbase, LDAP etc) and should also modify the current security context to contain the new password.
	 *
	 * @param oldPassword current password (for re-authentication if required)
	 * @param newPassword the password to change to
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void changePassword(String oldPassword, String newPassword) {
		final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

		final String userName = authentication.getName();
		final DistinguishedName userDN = userNameMapper.buildDn(userName);

		if(LOG.isDebugEnabled()) {
			LOG.debug("Changing password for current user: \"" + userName + "\"");
		}

		// Salt password from userName.getBytes() ?
		String newPasswordEncoded = passwordEncoder.encodePassword(newPassword, null);

		User theUser = userDAO.findByLogin(authentication.getName());

		theUser.setPassword(newPasswordEncoded);
		userDAO.update(theUser);

		// Updates a user
		final DirContextAdapter ctx = (DirContextAdapter)template.lookupContext(userDN);

		ctx.setUpdateMode(true);
		ctx.setAttributeValue(userPasswordAttribute, newPasswordEncoded);
		template.modifyAttributes(ctx);


		if(manageUsers) {
			// Change alfresco password
			alfrescoService.changePassword(userName, newPassword);
		}
	}

	/**
	 * Check if a user with the supplied login name exists in the system.
	 *
	 * @param username the user name.
	 *
	 * @return true if it exists, false otherwise.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public boolean userExists(String username) {
		return loadUserByUsername(username) != null;
	}

	/**
	 * Finds an organization by its identifier.
	 *
	 * @param id the organization id.
	 *
	 * @return the organization.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public Organization findOrganizationById(Integer id) {
		Organization result = organizationDAO.findById(id);

		if(result != null) {
			// Check if organization exists
			if(!groupExists(result.getName())) {
				createGroup(result.getName(), AuthorityUtils.NO_AUTHORITIES);
			}
		}

		return result;
	}

	/**
	 * Finds every organization.
	 *
	 * @return the organization list.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Organization> findAllOrganizations() {
		return organizationDAO.findAll();
	}

	/**
	 * Finds organizations by its name.
	 *
	 * @param name the name.
	 *
	 * @return the organization.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public Organization findOrganizationByName(String name) {
		Organization result = organizationDAO.findByName(name);

		if(result != null) {
			// Check if organization exists
			if(!groupExists(result.getName())) {
				createGroup(result.getName(), AuthorityUtils.NO_AUTHORITIES);
			}
		}

		return result;
	}

	/**
	 * Finds organizations by example.
	 *
	 * @param example the example criteria.
	 *
	 * @return the list of organizations.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Organization> findOrganizationsByExample(Map<String, Object> example) {
		return organizationDAO.findByExample(example);
	}

	/**
	 * Finds organizations by example.
	 *
	 * @param sample the example criteria.
	 * @param start the start.
	 * @param limit the limit.
	 * @param sortFields the sort fields.
	 * @param sortDirections the sort directions.
	 *
	 * @return the page of results.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public Page<Organization> findOrganizationsByExample(
		Map<String, Object> sample, int start, int limit, String[] sortFields, String[] sortDirections) {
		return organizationDAO.findByExample(sample, start, limit, sortFields, sortDirections);
	}

	/**
	 * Creates a new organization.
	 *
	 * @param organization the organization.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void createOrganization(final Organization organization) {
		organizationDAO.save(organization);

		// Check if organization exists
		if(!groupExists(organization.getName())) {
			createGroup(organization.getName(), AuthorityUtils.NO_AUTHORITIES);
		}
	}

	/**
	 * Updates an existing organization.
	 *
	 * @param organization the organization.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void updateOrganization(Organization organization) {
		organizationDAO.update(organization);

		// Check if organization exists
		if(!groupExists(organization.getName())) {
			createGroup(organization.getName(), AuthorityUtils.NO_AUTHORITIES);
		}
	}

	/**
	 * Retrieves the alfresco URL.
	 *
	 * @param organizationName the organization name (which also is the folder name).
	 *
	 * @return the URL.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public String getAlfrescoURL(String organizationName) {
		return alfrescoService.getOrganizationFolderURL(organizationName);
	}

	/**
	 * Returns the names of all groups that this group manager controls.
	 *
	 * @return the array of groups.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public String[] findAllGroups() {
		List<String> results = template.search(
				groupBase, groupFilter,
				new AbstractParameterizedContextMapper<String>() {
					/**
					 * Map a single <code>DirContextOperation</code> to an object. The supplied instance is the
					 * object supplied to {@link #mapFromContext(Object)} cast to a <code>DirContextOperations</code>.
					 *
					 * @param ctx the context to map to an object.
					 *
					 * @return an object built from the data in the context.
					 */
					protected String doMapFromContext(DirContextOperations ctx) {
						return ctx.getStringAttribute(groupNameAttribute);
					}
				});

		return results.toArray(new String[results.size()]);
	}

	/**
	 * Locates the users who are members of a group
	 *
	 * @param groupName the group whose members are required
	 *
	 * @return the usernames of the group members
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public String[] findUsersInGroup(String groupName) {
		final DistinguishedName groupDN = groupNameMapper.buildDn(groupName);

		DirContextAdapter ctx = (DirContextAdapter)template.lookupContext(groupDN);

		ctx.setUpdateMode(true);

		String[] members = ctx.getStringAttributes(groupMemberAttribute);

		String[] result = new String[members.length];
		DistinguishedName temp;

		for(int i = 0; i < members.length; ++i) {
			temp = new DistinguishedName(members[i]);
			result[i] = temp.removeFirst().getValue();
		}

		return result;
	}

	/**
	 * Checks if a given group exists.
	 *
	 * @param groupName the group name.
	 *
	 * @return true if the group exists, false otherwise.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public boolean groupExists(final String groupName) {
		final DistinguishedName groupDN = groupNameMapper.buildDn(groupName);

		try {
			return template.lookupContext(groupDN) != null;
		} catch(Throwable e) {
			return false;
		}
	}

	/**
	 * Creates a new group with the specified list of authorities.
	 *
	 * @param groupName the name for the new group
	 * @param authorities the authorities which are to be allocated to this group.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void createGroup(String groupName, GrantedAuthority[] authorities) {
		final DistinguishedName groupDN = groupNameMapper.buildDn(groupName);

		Organization organization = organizationDAO.findByName(groupName);

		// Creates a group
		final DirContextAdapter ctx = new DirContextAdapter(groupDN);

		organizationMapper.mapGroupToContext(organization, ctx);
		template.bind(ctx);

		if(manageUsers) {
			// Create alfresco group
			alfrescoService.createGroup(groupName);
		}
		// Check folder
		alfrescoService.checkOrganizationFolder(groupName);
	}

	/**
	 * Removes a group, including all members and authorities.
	 *
	 * @param groupName the group to remove.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void deleteGroup(String groupName) {
		Organization organization = organizationDAO.findByName(groupName);

		if(organization != null) {
			organizationDAO.remove(organization);
		}

		final DistinguishedName groupDN = groupNameMapper.buildDn(groupName);

		template.unbind(groupDN);

		if(manageUsers) {
			// Remove alfresco group
			alfrescoService.removeGroup(groupName);
		}
	}

	/**
	 * Changes the name of a group without altering the assigned authorities or members.
	 *
	 * @param oldName the old name.
	 * @param newName the new name.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void renameGroup(String oldName, String newName) {
		// Not implemented...
	}

	/**
	 * Makes a user a member of a particular group.
	 *
	 * @param username the user to be given membership.
	 * @param groupName the name of the group to which the user will be added.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void addUserToGroup(String username, String groupName) {
		final DistinguishedName userDN = userNameMapper.buildDn(username);
		final DistinguishedName groupDN = groupNameMapper.buildDn(groupName);

		// Maps the user to an organization
		DirContextAdapter groupCtx = (DirContextAdapter)template.lookupContext(groupDN);

		groupCtx.setUpdateMode(true);
		groupCtx.addAttributeValue(groupMemberAttribute, userDN.toCompactString(), true);
		template.modifyAttributes(groupCtx);

		if(manageUsers) {
			// Create alfresco group if does not exist
			if(!alfrescoService.groupExists(groupName)) {
				alfrescoService.createGroup(groupName);
			}

			if(!alfrescoService.hasGroup(username, groupName)) {
				// Add alfresco user to group
				alfrescoService.addUserToGroup(username, groupName);
			}
		}
	}

	/**
	 * Deletes a user's membership of a group.
	 *
	 * @param username the user
	 * @param groupName the group to remove them from
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void removeUserFromGroup(String username, String groupName) {
		final DistinguishedName userDN = userNameMapper.buildDn(username);
		final DistinguishedName groupDN = groupNameMapper.buildDn(groupName);

		// Maps the user to an organization
		DirContextAdapter groupCtx = (DirContextAdapter)template.lookupContext(groupDN);

		groupCtx.setUpdateMode(true);
		groupCtx.removeAttributeValue(groupMemberAttribute, userDN.toCompactString());
		template.modifyAttributes(groupCtx);

		if(manageUsers) {
			// Create alfresco group if does not exist
			if(!alfrescoService.groupExists(groupName)) {
				alfrescoService.createGroup(groupName);
			}

			if(alfrescoService.hasGroup(username, groupName)) {
				// Remove alfresco user to group
				alfrescoService.removeUserFromGroup(username, groupName);
			}
		}
	}

	/**
	 * Obtains the list of authorities which are assigned to a group.
	 *
	 * @param groupName the group name.
	 *
	 * @return the array of granted authorities.
	 */
	public GrantedAuthority[] findGroupAuthorities(String groupName) {
		return NO_AUTHORITIES;
	}

	/**
	 * Assigns a new authority to a group.
	 *
	 * @param groupName the group name.
	 * @param authority the authority.
	 */
	public void addGroupAuthority(String groupName, GrantedAuthority authority) {
		// Do nothing
	}

	/**
	 * Deletes an authority from those assigned to a group
	 *
	 * @param groupName the group name.
	 * @param authority the authority.
	 */
	public void removeGroupAuthority(String groupName, GrantedAuthority authority) {
		// Do nothing
	}
}
